using System;
using System.Collections.Generic;
using System.Linq;
using Db4objects.Db4o.Linq;
using SharedObjects;
using TheGame.Server;
using TheGameLib;

namespace TheGame.Webservices
{
	public partial class TheGameService
	{
		#region Banking
		public long CreateBankAccount(Guid sessionId, ulong systemId)
		{
			Agent me;
			if (!CheckIsValidAgent(sessionId, out me))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return -1;
			}
			//connection verification
			AgentSessionObject aso = World.Instance.GetAgentSession(sessionId);
			if (aso == null)
			{
				SendServerError(ServerErrorSeverity.Error, "No session object for session id.");
				return -1;
			}
			AgentConnection conn = aso.GetAgentConnectionByTargetSystem(systemId);
			if (conn == null)
			{
				SendServerError(ServerErrorSeverity.Error, "No connection to target system.");
				return -1;
			}

			if ((from x in me.BankAccounts where x.SystemId == systemId select x).Count() > 0)
			{
				SendServerError(ServerErrorSeverity.Error, "Can not have more than 1 bank account on this system.");
				return -1;
			}

			long accountNumber = 0;
			while (!CheckIsValidAccountNumber(accountNumber, systemId))
			{
				byte[] bytes = Guid.NewGuid().ToByteArray();
				for (int i = 0; i < 4; i++)
					accountNumber += BitConverter.ToUInt32(bytes, i * 4);
			}
			BankAccount account = new BankAccount { Id = Guid.NewGuid().ToLong(), CachedAmount = 0, IsDeleted = false, SystemId = systemId, AccountNumber = accountNumber };
			Db.Store(account);
			Db.Commit();
			return account.AccountNumber;
		}

		public List<BankAccount> GetBankAccount(Guid sessionId, ulong systemId)
		{
			if (!CheckIsValidAgent(sessionId))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return null;
			}
			//connection verification
			AgentSessionObject aso = World.Instance.GetAgentSession(sessionId);
			if (aso == null)
			{
				SendServerError(ServerErrorSeverity.Error, "No session object for session id.");
				return null;
			}
			AgentConnection conn = aso.GetAgentConnectionByTargetSystem(systemId);
			if (conn == null)
			{
				SendServerError(ServerErrorSeverity.Error, "No connection to target system.");
				return null;
			}
			ComputerSystem cs = (from ComputerSystem c in Db where c.Id == systemId select c).FirstOrDefault();
			if (cs == null)
				return null;
			if (!conn.IsHacked)
			{
				BankAccount account = (from BankAccount ba in aso.AgentInDb.BankAccounts where ba.SystemId == systemId select ba).FirstOrDefault();
				if (account == null)
					account = (from Agent a in Db where a.Id == aso.AgentInDb.Id from BankAccount ba in a.BankAccounts where ba.SystemId == systemId select ba).FirstOrDefault();
				if (account == null)
				{
					SendServerError(ServerErrorSeverity.Warning, "No account found on this system.");
					return null;
				}

				account.CachedAmount = GetVerifiedBankAccountAmount(account.Id);
				return new List<BankAccount> { account };
			}
			cs.Log(Db, conn.GetConnectedHostId(conn.TargetSystemId), LoggingAction.HackBankAccounts, null);
			List<BankAccount> retVal = new List<BankAccount> { null };
			retVal.AddRange((from BankAccount ba in Db where ba.SystemId == systemId && !ba.IsInvisible && !ba.IsDeleted select ba));
			return retVal;
		}

		public List<ReadonlyBankTransaction> GetBankAccountMutations(Guid sessionId, ulong systemId, long accountNumber)
		{
			if (!CheckIsValidAgent(sessionId))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return null;
			}
			//connection verification
			AgentSessionObject aso = World.Instance.GetAgentSession(sessionId);
			if (aso == null)
			{
				SendServerError(ServerErrorSeverity.Error, "No session object for session id.");
				return null;
			}
			AgentConnection conn = aso.GetAgentConnectionByTargetSystem(systemId);
			if (conn == null)
			{
				SendServerError(ServerErrorSeverity.Error, "No connection to target system.");
				return null;
			}

			BankAccount account = GetBankAccountByAccountNumber(accountNumber, systemId);
			if (account == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Specified bank account does not exist.");
				return null;
			}

			var list = (from BankTransaction tr in Db where tr.ReceivingAccountId == account.Id select new ReadonlyBankTransaction
				{
					Id = tr.Id,
					Amount = tr.Amount,
					Date = tr.TransactionTimeStamp,
					Purpose = tr.Purpose,
					TransactionPartnerAccountNumber = tr.ReceivingAccountId == account.Id ? GetAccountNumberById(tr.SendingAccountId) : GetAccountNumberById(tr.ReceivingAccountId),
					TransactionPartnerBankName = tr.ReceivingAccountId == account.Id ? GetAccountBankNameById(tr.SendingAccountId) : GetAccountBankNameById(tr.ReceivingAccountId)
				}
			).ToList();

			list.AddRange((from BankTransaction tr in Db where tr.SendingAccountId == account.Id select new ReadonlyBankTransaction
			{
				Id = tr.Id,
				Amount = (tr.Amount * (-1)),
				Date = tr.TransactionTimeStamp,
				Purpose = tr.Purpose,
				TransactionPartnerAccountNumber = tr.ReceivingAccountId == account.Id ? GetAccountNumberById(tr.SendingAccountId) : GetAccountNumberById(tr.ReceivingAccountId),
				TransactionPartnerBankName = tr.ReceivingAccountId == account.Id ? GetAccountBankNameById(tr.SendingAccountId) : GetAccountBankNameById(tr.ReceivingAccountId)
			}));
			return list.OrderByDescending(it => it.Date).ToList();
		}

		public bool CreateTransaction(Guid sessionId, ulong systemId, long accountNumber, long receivingAccountNumber, string bankName, long amount, string purpose)
		{
			//user verification
			if (!CheckIsValidAgent(sessionId))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return false;
			}
			//connection verification
			AgentSessionObject aso = World.Instance.GetAgentSession(sessionId);
			if (aso == null)
			{
				SendServerError(ServerErrorSeverity.Error, "No session object for session id.");
				return false;
			}
			AgentConnection conn = aso.GetAgentConnectionByTargetSystem(systemId);
			if (conn == null)
			{
				SendServerError(ServerErrorSeverity.Error, "No connection to target system.");
				return false;
			}

			//verify if sending account exists.
			BankAccount sendingAccount;
			if (conn.IsHacked) //we get the sending account regardless of if it's the agent's account
			{
				sendingAccount = (from BankAccount ba in Db where ba.AccountNumber == accountNumber && ba.SystemId == systemId select ba).FirstOrDefault();
			}
			else //we only allow the agent's account to be used.
			{
				sendingAccount =
					(
					from Agent a in Db
					where a.Id == aso.AgentInDb.Id
					from BankAccount ba in a.BankAccounts
					where ba.AccountNumber == accountNumber &&
					ba.SystemId == systemId
					select ba
					).FirstOrDefault();
			}

			if (sendingAccount == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Source bank account does not exist.");
				return false;
			}

			//verify if sending account has required amount of money for transaction
			if (GetVerifiedBankAccountAmount(sendingAccount.Id) < amount)
			{
				SendServerError(ServerErrorSeverity.Error, "Source bank account does not have sufficient funds.");
				return false;
			}

			//verify if receiving account exists.
			ulong receivingBankAccountSystemId = GetBankAccountSystemId(bankName);
			if (receivingBankAccountSystemId == 0)
			{
				SendServerError(ServerErrorSeverity.Error, "Specified target bank does not exist.");
				return false;
			}
			
			BankAccount receivingAccount = (from BankAccount ba in Db where ba.AccountNumber == receivingAccountNumber && ba.SystemId == receivingBankAccountSystemId select ba).FirstOrDefault();
			if (receivingAccount == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Specified target account does not exist.");
				return false;
			}

			long receivingAccountBalance = GetVerifiedBankAccountAmount(receivingAccount.Id);
			if(receivingAccountBalance == long.MaxValue)
			{
				SendServerError(ServerErrorSeverity.Error, "Target account balance is at maximum value. No transactions to this account are possible.");
				return false;
			}

			//lock both accounts in the db.
			if (!Db.Ext().SetSemaphore(sendingAccount.Id.ToString(), 1000))
			{
				SendServerError(ServerErrorSeverity.Error, "Could not get lock on source bank account. Please try again.");
				return false;
			}
			if (!Db.Ext().SetSemaphore(receivingAccount.Id.ToString(), 1000))
			{
				SendServerError(ServerErrorSeverity.Error, "Could not get lock on target bank account. Please try again.");
				return false;
			}

			//create new transaction for sender account with amount deducted, timestamp, receiver etc
			BankTransaction transaction = new BankTransaction
			{
				Amount = amount,
				Id = Guid.NewGuid().ToLong(),
				IsDeleted = false,
				Purpose = purpose,
				ReceivingAccountId = receivingAccount.Id,
				SendingAccountId = sendingAccount.Id,
				TransactionTimeStamp = World.Instance.WorldTime
			};

			Db.PersistEntity(transaction);
			sendingAccount.CachedAmount = GetVerifiedBankAccountAmount(sendingAccount.Id);
			receivingAccount.CachedAmount = GetVerifiedBankAccountAmount(receivingAccount.Id);
			Db.Store(sendingAccount);
			Db.Store(receivingAccount);
			Db.Commit();
			Db.Ext().ReleaseSemaphore(sendingAccount.Id.ToString());
			Db.Ext().ReleaseSemaphore(receivingAccount.Id.ToString());

			string senderString = sendingAccount.AccountNumber + "@" + GetAccountBankNameById(sendingAccount.Id);
			string receiverString = receivingAccount.AccountNumber + "@" + GetAccountBankNameById(receivingAccount.Id);

			//notify the sender of the deduction
			aso.NotifyBankTransaction(
				transaction.Amount * -1,
				senderString,
				receiverString,
				transaction.TransactionTimeStamp);

			//notify the recipient of the incoming amoun
			Agent receiver = (from Agent a in Db from BankAccount ba in a.BankAccounts where ba.Id == receivingAccount.Id select a).FirstOrDefault();
			if (receiver == null)
			{
				SendServerError(ServerErrorSeverity.Error, "No agent was associated with the target bank account. Transaction was completed successfully in either case.");
				return true;
			}

			AgentSessionObject receiverSession = (from session in World.Instance.AgentSessions where session.Value.AgentInDb.Id == receiver.Id select session.Value).FirstOrDefault();
			if (receiverSession == null) //then agent is currently offline. Store the notifications for him.
			{
				QueuedTransactionNotification qtm = new QueuedTransactionNotification
				{
					AgentId = receiver.Id,
					DestinationAccountInfo = receiverString,
					Id = Guid.NewGuid().ToLong(),
					SourceAccountInfo = senderString,
					TransactionAmount = transaction.Amount,
					TransactionDate = transaction.TransactionTimeStamp
				};
				Db.PersistEntity(qtm);
				Db.Commit();
			}
			else
			{
				receiverSession.NotifyBankTransaction(transaction.Amount, senderString, receiverString, transaction.TransactionTimeStamp);
			}
			return true;
		}

		public bool VerifyTransactionRecipient(Guid sessionId, int accountNumber, string bankName)
		{
			if (!CheckIsValidAgent(sessionId))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return false;
			}

			ComputerSystem cs = GetHostByAddress(bankName);
			if (cs == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Bank name did not resolve to an existing computer system.");
				return false;
			}

			BankAccount ba = GetBankAccountByAccountNumber(accountNumber, cs.Id);
			if (ba == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Bank account with specified number does not exist on this system.");
				return false;
			}
			return true;
		}
		#endregion

		#region Market
		public long GetAvailableFunds(Guid sessionId, ulong systemId)
		{
			Agent me;
			AgentSessionObject aso;
			AgentConnection conn;
			if (!ValidateAgentSessionAndConnection(sessionId, systemId, out me, out aso, out conn))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id, session object or connection.");
				return 0;
			}

			ComputerSystem sys = (from ComputerSystem cs in Db where cs.Id == systemId select cs).FirstOrDefault();
			if (sys == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Target system not found.");
				return 0;
			}

			ComputerSystemFeature csfMarket = (from ComputerSystemFeature csf in sys.ComputerSystemFeatures where csf.Name == "Market" select csf).FirstOrDefault();
			if (csfMarket == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Specified system does not have a market.");
				return 0;
			}

			long totalFunds = 0;
			var accounts = (from Agent a in Db where a.Id == me.Id from BankAccount ba in a.BankAccounts select ba);
			foreach (BankAccount ba in accounts)
			{
				long verifiedAmount = GetVerifiedBankAccountAmount(ba.Id);
				if (verifiedAmount != ba.CachedAmount)
				{
					ba.CachedAmount = verifiedAmount;
					Db.Store(ba);
				}
				totalFunds += ba.CachedAmount;
			}
			return totalFunds;
		}
		
		public List<MarketItem> GetAvailableSoftware(Guid sessionId, ulong systemId)
		{
			Agent me;
			AgentSessionObject aso;
			AgentConnection conn;
			if (!ValidateAgentSessionAndConnection(sessionId, systemId, out me, out aso, out conn))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id, session object or connection.");
				return null;
			}

			ComputerSystem sys = (from ComputerSystem cs in Db where cs.Id == systemId select cs).FirstOrDefault();
			if (sys == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Target system not found.");
				return null;
			}

			ComputerSystemFeature csfMarket = (from ComputerSystemFeature csf in sys.ComputerSystemFeatures where csf.Name == "Market" select csf).FirstOrDefault();
			if (csfMarket == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Specified system does not have a market.");
				return null;
			}

			//the more difficult a system is to hack, the lower the prices for its items (risk vs reward).
			List<MarketItem> retval = (from Program p in Db
			               where !p.IsDeleted && !p.IsDefault && p.MinimumDifficulty <= sys.DifficultyLevel
			               select new MarketItem
			               {
			               	ItemId = p.Id,
			               	Name = p.Name,
			               	Price = CalculateItemPrice(p.BasePrice, sys.DifficultyLevel),
			               	ItemType = MarketItemType.Software,
			               	Modifier = p.Version,
			               	Size = p.Size
			               }).ToList();

			return retval;
		}

		public List<MarketItem> GetAvailableHardware(Guid sessionId, ulong systemId)
		{
			Agent me;
			AgentSessionObject aso;
			AgentConnection conn;
			if (!ValidateAgentSessionAndConnection(sessionId, systemId, out me, out aso, out conn))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id, session object or connection.");
				return null;
			}

			ComputerSystem sys = (from ComputerSystem cs in Db where cs.Id == systemId select cs).FirstOrDefault();
			if (sys == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Target system not found.");
				return null;
			}

			ComputerSystemFeature csfMarket = (from ComputerSystemFeature csf in sys.ComputerSystemFeatures where csf.Name == "Market" select csf).FirstOrDefault();
			if (csfMarket == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Specified system does not have a market.");
				return null;
			}

			//note: the more difficult a system, the lower the prices for its items.
			List<MarketItem> retval = (from Cpu hwb in Db
				where !hwb.IsDeleted && hwb.Name != null && hwb.MinimumMarketDifficulty <= sys.DifficultyLevel //&& !hwb.IsDefault 
				select new MarketItem
				{
					ItemId = hwb.Id,
					Name = hwb.Name,
					Price = CalculateItemPrice(hwb.BasePrice, sys.DifficultyLevel),
					ItemType = MarketItemType.Cpu,
					Modifier = (int)hwb.Modifier
				}).ToList();
			retval.AddRange((from MemoryType hwb in Db
				where !hwb.IsDeleted && hwb.Name != null && hwb.MinimumMarketDifficulty <= sys.DifficultyLevel //&& !hwb.IsDefault
				select new MarketItem
				{
					ItemId = hwb.Id,
					Name = hwb.Name,
					Price = CalculateItemPrice(hwb.BasePrice, sys.DifficultyLevel),
					ItemType = MarketItemType.Memory,
					Modifier = (int)hwb.Modifier
				}));
			retval.AddRange((from NetworkAdapter hwb in Db
				where !hwb.IsDeleted && hwb.Name != null && hwb.MinimumMarketDifficulty <= sys.DifficultyLevel //&& !hwb.IsDefault
				select new MarketItem
				{
					ItemId = hwb.Id,
					Name = hwb.Name,
					Price = CalculateItemPrice(hwb.BasePrice, sys.DifficultyLevel),
					ItemType = MarketItemType.Nic,
					Modifier = (int)hwb.Modifier
				}));

			retval.AddRange((from GatewayBlueprint gwp in Db
				where !gwp.IsDeleted && gwp.Name != null && gwp.MinimumMarketDifficulty <= sys.DifficultyLevel && !gwp.IsDefault
				select new MarketItem
				{
					ItemId = gwp.Id,
					ItemType = MarketItemType.Blueprint,
					Name = gwp.Name,
					Price = CalculateItemPrice(gwp.BasePrice, sys.DifficultyLevel),
					Modifier = (int)gwp.Modifier,
					Description = gwp.GetDescription()
				}));
			return retval;
		}

		public bool PurchaseProgram(Guid sessionId, ulong systemId, ulong itemId)
		{
			Agent me;
			AgentSessionObject aso;
			AgentConnection conn;
			if (!ValidateAgentSessionAndConnection(sessionId, systemId, out me, out aso, out conn))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id, session object or connection.");
				return false;
			}

			ComputerSystem marketSystem = (from ComputerSystem cs in Db where cs.Id == systemId select cs).FirstOrDefault();
			if (marketSystem == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Target system not found.");
				return false;
			}

			ComputerSystemFeature csfMarket = (from ComputerSystemFeature csf in marketSystem.ComputerSystemFeatures where csf.Name == "Market" select csf).FirstOrDefault();
			if (csfMarket == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Specified system does not have a market.");
				return false;
			}

			var ebItem = (from Program b in Db where b.Id == itemId select b).FirstOrDefault();
			if (ebItem == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Specified program not found.");
				return false;
			}


			if (CanDeliverSoftware(me, ebItem))
			{
				if (!PurchaseTradableItem(me, marketSystem, ebItem))
					return false;
				DeliverSoftware(me, ebItem);
				Db.Commit();
				return true;
			}
			return false;
		}

		public bool PurchaseHardware(Guid sessionId, ulong systemId, ulong itemId)
		{
			Agent me;
			AgentSessionObject aso;
			AgentConnection conn;
			if (!ValidateAgentSessionAndConnection(sessionId, systemId, out me, out aso, out conn))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id, session object or connection.");
				return false;
			}

			ComputerSystem marketSystem = (from ComputerSystem cs in Db where cs.Id == systemId select cs).FirstOrDefault();
			if (marketSystem == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Target system not found.");
				return false;
			}

			ComputerSystemFeature csfMarket = (from ComputerSystemFeature csf in marketSystem.ComputerSystemFeatures where csf.Name == "Market" select csf).FirstOrDefault();
			if (csfMarket == null)
			{		
				SendServerError(ServerErrorSeverity.Error, "Specified system does not have a market.");
				return false;
			}

			var ebItem = (from EntityBase b in Db where b.Id == itemId select b).FirstOrDefault();
			if (ebItem == null || !(ebItem is IHardware))
			{
				SendServerError(ServerErrorSeverity.Error, "Specified program not found.");
				return false;
			}

			if (!PurchaseTradableItem(me, marketSystem, ebItem))
				return false;

			IHardware item = ebItem as IHardware;
			me.OwnedHardware.Add(item);
			Db.PersistEntity(me);
			Db.Commit();
			return true;
		}

		private void DeliverSoftware(Agent agent, Program ebItem)
		{
			//check that agent's gateway has enough space left to purchase.
			Gateway targetGateway = (from Gateway gw in Db where gw.Id == agent.Gateway.Id select gw).FirstOrDefault();
			if (targetGateway == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Could not find target gateway!");
				return;
			}
			int totalMemory = targetGateway.GetTotalMemory();
			ComputerSystem targetSystem = (from ComputerSystem cs in Db where cs.Id == agent.Gateway.ComputerSystem.Id select cs).FirstOrDefault();
			if (targetSystem == null)
				throw new Exception("DeliverSoftware: Invalid target system encountered.");

			int usedMemory = targetSystem.RootDirectory.GetUsedMemory(Db);
			if ((totalMemory - usedMemory) < ebItem.Size)
				throw new Exception("DeliverSoftware: Not enough memory capacity.");

			ProgramFile pf = new ProgramFile
			{
				AllowModify = ebItem.AllowModification,
				CompressionLevel = 0,
				DisplaySize = ebItem.Size,
				EncryptionLevel = 0,
				Id = Guid.NewGuid().ToLong(),
				IsLocked = false,
				Name = ebItem.Name,
				Size = ebItem.Size,
				Program = ebItem,
				Time = World.Instance.WorldTime,
				Version = ebItem.Version,
				Type = FileType.Program,
				ParentId = targetSystem.RootDirectory.Id
			};

			Db.PersistEntity(pf);
		}

		private bool CanDeliverSoftware(Agent agent, Program ebItem)
		{
			//check that agent's gateway has enough space left to purchase.
			Gateway targetGateway = (from Gateway gw in Db where gw.Id == agent.Gateway.Id select gw).FirstOrDefault();
			if (targetGateway == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Target gateway not found.");
				return false;
			}
			int totalMemory = targetGateway.GetTotalMemory();
			ComputerSystem targetSystem = (from ComputerSystem cs in Db where cs.Id == agent.Gateway.ComputerSystem.Id select cs).FirstOrDefault();
			if (targetSystem == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Target gateway computer system not found.");
				return false;
			}
			int usedMemory = targetSystem.RootDirectory.GetUsedMemory(Db);
			if ((totalMemory - usedMemory) < ebItem.Size)
			{
				SendServerError(ServerErrorSeverity.Error, "Not enough memory left on target gateway.");
				return false;
			}
			return true;
		}

		private bool PurchaseTradableItem(Agent agent, ComputerSystem marketSystem, EntityBase item)
		{
			var itemToPurchase = (from EntityBase p in Db where p.Id == item.Id select p).FirstOrDefault();
			if (itemToPurchase == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Purchased item does not exist.");
				return false;
			}
			
			BankAccount targetBankAccount = (from BankAccount ba in Db where ba.IsSystem && ba.SystemId == marketSystem.Id select ba).FirstOrDefault();
			if (targetBankAccount == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Market system has no bank account.");
				return false;
			}

			long totalPrice;
			string transactionPurpose;

			if (itemToPurchase is Program)
			{
				Program prog = itemToPurchase as Program;
				totalPrice = CalculateItemPrice(prog.BasePrice, marketSystem.DifficultyLevel);

				transactionPurpose = string.Format(
					"Purchased program {0} v{1}.0 from {2}.",
					prog.Name,
					prog.Version,
					GetBankAccountStringById(targetBankAccount.Id));
			}
			else if (itemToPurchase is IHardware)
			{
				IHardware hardware = itemToPurchase as IHardware;
				totalPrice = CalculateItemPrice(hardware.BasePrice, marketSystem.DifficultyLevel);
				transactionPurpose = string.Format(
					"Purchased {0} from {1}.",
					hardware.Name,
					GetBankAccountStringById(targetBankAccount.Id)
					);
			}
			else
			{
				SendServerError(ServerErrorSeverity.Error, "Purchased item is neither hardware nor software.");
				return false;
			}

			//check for sufficient funds on all the user's bank accounts.
			long totalFunds = 0;
			var accounts = (from Agent a in Db where a.Id == agent.Id from BankAccount ba in a.BankAccounts select ba);
			foreach (BankAccount ba in accounts)
			{
				long verifiedAmount = GetVerifiedBankAccountAmount(ba.Id);
				if (verifiedAmount != ba.CachedAmount)
				{
					ba.CachedAmount = verifiedAmount;
					Db.Store(ba);
				}
				totalFunds += ba.CachedAmount;
			}

			if (totalPrice > totalFunds)
			{
				SendServerError(ServerErrorSeverity.Error, "You do not have enough funds to make the purchase.");
				return false;
			}

			accounts = accounts.OrderByDescending(it => it.CachedAmount);
			foreach(BankAccount ba in accounts)
			{
				if(ba.CachedAmount >= totalPrice) //this bank account has enough funds for the (remaining) total price.
				{
					BankTransaction transaction = new BankTransaction
					{
						Amount = totalPrice,
						Id = Guid.NewGuid().ToLong(),
						IsDeleted = false,
						Purpose = transactionPurpose,
						ReceivingAccountId = targetBankAccount.Id,
						SendingAccountId = accounts.First().Id,
						TransactionTimeStamp = World.Instance.WorldTime
					};
					if (!Db.Ext().SetSemaphore(accounts.First().Id.ToString(), 1000))
					{
						SendServerError(ServerErrorSeverity.Fatal, "Could not set lock on source bank account!");
						return false;
					}
					if (!Db.Ext().SetSemaphore(targetBankAccount.Id.ToString(), 1000))
					{
						SendServerError(ServerErrorSeverity.Fatal, "Could not set lock on target bank account!");
						return false;
					}

					Db.PersistEntity(transaction);
					accounts.First().CachedAmount = GetVerifiedBankAccountAmount(accounts.First().Id);
					targetBankAccount.CachedAmount = GetVerifiedBankAccountAmount(targetBankAccount.Id);
					Db.Store(accounts.First());
					Db.Store(targetBankAccount);
					Db.Commit();
					Db.Ext().ReleaseSemaphore(accounts.First().Id.ToString());
					Db.Ext().ReleaseSemaphore(targetBankAccount.Id.ToString());
					return true;
				}
				else
				{
					totalPrice -= ba.CachedAmount;
					BankTransaction transaction = new BankTransaction
					{
						Amount = ba.CachedAmount,
						Id = Guid.NewGuid().ToLong(),
						IsDeleted = false,
						Purpose = transactionPurpose,
						ReceivingAccountId = targetBankAccount.Id,
						SendingAccountId = ba.Id,
						TransactionTimeStamp = World.Instance.WorldTime
					};
					Db.PersistEntity(transaction);
				}
			}
			throw new InvalidOperationException("Error: software purchase didn't complete successfully due to insufficient funds. Error in serverside logic likely.");
		}

		private long CalculateItemPrice(long basePrice, int difficultyLevel)
		{
			return (long)(basePrice / (Math.Pow(1.1, difficultyLevel)));
		}
		#endregion
	}
}